package org.lisy.java.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;

/**
 * 压缩文件操作 
 *  - 不支持 4GB 以上的大文件
 * 
 * @author lisy
 */
public class ZipOperate {

	// 原始压缩文件目录
	private final static String RIGINAL_ZIP_FILE_DIR = "./deploy/riginal";
	// 原始压缩文件
	private final static String RIGINAL_ZIP_FILE = "riginal.zip";
	// 目标压缩文件目录
	private final static String TARGET_ZIP_FILE_DIR = "./deploy/target/compressed";
	// 目标压缩文件
	private final static String TARGET_ZIP_FILE = "target.zip";

	public static void main(String[] args) {
		int type = 4;
		switch (type) {
			case 1:
				// 解压，压缩文件-不生成临时文件，不保留目录结构
				noFileNoStruct(type);
				break;
			case 2:
				// 解压，压缩文件-不生成临时文件，保留目录结构
				noFileRetainStruct(type);
				break;
			case 3:
				// 解压，压缩文件-生成临时文件，不保留目录结构 
				tempFileNoStruct(type);
				break;
			case 4:
				// 解压，压缩文件-生成临时文件，保留目录结构
				tempFileRetainStruct(type);
				break;
			default:
				System.out.println("invalid type!");
				break;
		}
	}


	/**
	 * 解压，压缩文件-不生成临时文件，不保留目录结构
	 * @param type
	 */
	private static void noFileNoStruct(int type) {
		System.out.println("========== start noFileNoStruct ==========");
		List<String> fileNames = new ArrayList<String>();
		// 获取路径下文件流列表
		List<byte[]> fileBytes = getFileBytes(fileNames);
		System.out.println("get file stream list complate! count: " + fileBytes.size());
		// 将多个字节流打包成压缩字节流
		byte[] zipByte = zipFiles(fileBytes, fileNames);
		System.out.println("packed compressed file byte stream complate!");
		// 根据字节流生成压缩文件
		genZipFileByByte(zipByte, type);
		System.out.println("generate compressed file complate!");
	}

	private static List<byte[]> getFileBytes(List<String> fileNames) {
		List<byte[]> srcBytes = new ArrayList<byte[]>();
		ZipFile zipFile = null;
		try {
			String path = RIGINAL_ZIP_FILE_DIR + File.separator + RIGINAL_ZIP_FILE;
			zipFile = new ZipFile(path, "gbk");
			Enumeration<? extends ZipEntry> zList = zipFile.getEntries();
			ZipEntry ze = null;
			byte[] buf = new byte[1024];
			while (zList.hasMoreElements()) {
				ze = (ZipEntry) zList.nextElement();
				if (ze.isDirectory()) {
					continue;
				}
				ByteArrayOutputStream output = null;
				try {
					output = new ByteArrayOutputStream();
					InputStream is = new BufferedInputStream(zipFile.getInputStream(ze));
					fileNames.add(ze.getName());
					int readLen = 0;
					while ((readLen = is.read(buf, 0, 1024)) != -1) {
						output.write(buf, 0, readLen);
					}
					byte[] byteArray = output.toByteArray();
					srcBytes.add(byteArray);
					is.close();
					output.close();
				} finally {
					if (output != null) {
						output.close();
					}
				}
			}
			zipFile.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(e.toString());
		} finally {
			if (zipFile != null) {
				try {
					zipFile.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return srcBytes;
	}

	public static byte[] zipFiles(List<byte[]> srcBytes, List<String> fileNames) {
		byte[] bs = null;
		ByteArrayOutputStream bos = null;
		ZipOutputStream zos = null;
		try {
			bos = new ByteArrayOutputStream();
			zos = new ZipOutputStream(bos);
			zos.setEncoding("GBK");
			for (int i = 0; i < srcBytes.size(); i++) {
				byte[] bytes = srcBytes.get(i);
				// 获取文件名和后缀
				String fileName = fileNames.get(i);
				String suffix = fileName.substring(fileName.lastIndexOf("."));
				String name = null;
				if (fileName.contains("/")) {
					name = fileName.substring(fileName.lastIndexOf("/") + 1, fileName.lastIndexOf("."));
				} else {
					name = fileName.substring(0, fileName.lastIndexOf("."));
				}
				// 压缩文件
				System.out.println("prepare packet file: " + name + "(" + i + ")" + suffix);
				ZipEntry entry = new ZipEntry(name + "(" + i + ")" + suffix);
				zos.putNextEntry(entry);
				zos.write(bytes);
			}
			zos.closeEntry();
			zos.close();
			bs = bos.toByteArray();
			bos.close();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println(e.toString());
		} finally {
			if (zos != null) {
				try {
					zos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return bs;
	}

	private static void genZipFileByByte(byte[] zipFiles, int type) {
		BufferedOutputStream bos = null;
		FileOutputStream fos = null;
		File file = null;
		try {
			File dir = new File(TARGET_ZIP_FILE_DIR);
			if (!dir.exists()) {
				dir.mkdirs();
			}
			file = new File(TARGET_ZIP_FILE_DIR + File.separator + TARGET_ZIP_FILE.replace(".zip", "_" + type + ".zip"));
			System.out.println("prepare generate compressed file: " + file.getName());
			fos = new FileOutputStream(file);
			bos = new BufferedOutputStream(fos);
			bos.write(zipFiles);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 解压，压缩文件-不生成临时文件，保留目录结构
	 * @param type
	 */
	private static void noFileRetainStruct(int type) {
		System.out.println("========== start noFileRetainStruct ==========");
		// 解析压缩文件-获取压缩文件字节流
		byte[] zipByte = getZipBytes();
		System.out.println("packed compressed file byte stream complate!");
		if (zipByte != null) {
			// 根据字节流生成压缩文件
			genZipFileByByte(zipByte, type);
			System.out.println("generate compressed file complate!");
		}
	}
	
	private static byte[] getZipBytes() {
		byte[] zipByte = null;
		ZipFile zipFile = null;
		ZipOutputStream zos = null;
		ByteArrayOutputStream bos = null;
		try {
			bos = new ByteArrayOutputStream();
			zos = new ZipOutputStream(bos);
			zos.setEncoding("GBK");
			
			String path = RIGINAL_ZIP_FILE_DIR + File.separator + RIGINAL_ZIP_FILE;
			zipFile = new ZipFile(path, "gbk");
			Enumeration<? extends ZipEntry> zList = zipFile.getEntries();
			while (zList.hasMoreElements()) {
				ZipEntry ze = (ZipEntry) zList.nextElement();
				if (ze.isDirectory()) {
					System.out.println("prepare packet compressed directory: " + ze.getName());
					zos.putNextEntry(new ZipEntry(ze.getName()));
					zos.closeEntry();
					continue;
				}
				System.out.println("prepare packet compressed file: " + ze.getName());
				InputStream in = null;
				try {
					zos.putNextEntry(new ZipEntry(ze.getName()));
					in = new BufferedInputStream(zipFile.getInputStream(ze));
					byte[] buf = new byte[1024];
					int readLen = 0;
					while ((readLen = in.read(buf)) != -1) {
						zos.write(buf, 0, readLen);
						zos.setEncoding("gbk");
					}
					zos.closeEntry();
					in.close();
				} finally {
					if (in != null) {
						in.close();
					}
				}
			}
			zipFile.close();
			zos.close();
			zipByte = bos.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(e.toString());
			return null;
		} finally {
			if (zipFile != null) {
				try {
					zipFile.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (zos != null) {
				try {
					zos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return zipByte;
	}

	/**
	 * 解压，压缩文件-生成临时文件，不保留目录结构 
	 * @param type
	 */
	private static void tempFileNoStruct(int type) {
		System.out.println("========== start tempFileNoStruct ==========");
		// 解析压缩文件
		String rootPath = unZipFile();
		System.out.println("unzip files complate! temporary files path: " + rootPath);
		if (StringUtils.isNotBlank(rootPath)) {
			// 生成压缩文件
			compressedFileByPath(rootPath, type, false);
			System.out.println("generate compressed file complate!");
			// 删除临时文件
			boolean delFiles = delFiles(rootPath);
			System.out.println("delete temporary file: " + delFiles);
		}
	}
	
	/**
	 * 解压，压缩文件-生成临时文件，保留目录结构
	 * @param type 
	 * @param type
	 */
	private static void tempFileRetainStruct(int type) {
		System.out.println("========== start tempFileRetainStruct ==========");
		// 解析压缩文件
		String rootPath = unZipFile();
		System.out.println("unzip files complate! temporary files path: " + rootPath);
		if (StringUtils.isNotBlank(rootPath)) {
			// 生成压缩文件
			compressedFileByPath(rootPath, type, true);
			System.out.println("generate compressed file complate!");
			// 删除临时文件
			boolean delFiles = delFiles(rootPath);
			System.out.println("delete temporary file: " + delFiles);
		}
	}

	public static String unZipFile() {
		String rootPath = null;
		ZipFile zipFile = null;
		OutputStream os = null;
		InputStream is = null;
		try {
			String path = RIGINAL_ZIP_FILE_DIR + File.separator + RIGINAL_ZIP_FILE;
			File zipF = new File(path);
			rootPath = RIGINAL_ZIP_FILE_DIR + File.separator + zipF.getName().replace(".zip", "_") + System.currentTimeMillis();
			File firstFile = new File(rootPath);
			firstFile.mkdir();
			
			zipFile = new ZipFile(path, "gbk");
			Enumeration<? extends ZipEntry> zList = zipFile.getEntries();
			byte[] buf = new byte[1024];
			while (zList.hasMoreElements()) {
				ZipEntry ze = (ZipEntry) zList.nextElement();
				if (ze.isDirectory()) {
					File f = new File(rootPath + File.separator + ze.getName());
					f.mkdir();
					continue;
				}
				// 给定根目录，返回一个相对路径所对应的实际文件名
				File realFileName = getRealFileName(rootPath + File.separator, ze.getName());
				os = new BufferedOutputStream(new FileOutputStream(realFileName));
				is = new BufferedInputStream(zipFile.getInputStream(ze));
				int readLen = 0;
				while ((readLen = is.read(buf, 0, 1024)) != -1) {
					os.write(buf, 0, readLen);
				}
				is.close();
				os.close();
			}
			zipFile.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(e.toString());
			return null;
		} finally {
			if (zipFile != null) {
				try {
					zipFile.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (os != null) {
				try {
					os.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return rootPath;
	}

	public static File getRealFileName(String baseDir, String absFileName) {
		// 保证文件路径创建完成
		String[] dirs = absFileName.split("/");
		if (dirs.length > 1) {
			File ret = null;
			String tempDir = baseDir;
			for (int i = 0; i < dirs.length - 1; i++) {
				ret = new File(tempDir, dirs[i]);
				if (!ret.exists()) {
					ret.mkdirs();
				}
				tempDir += dirs[i];
			}
		}
		// 实际文件
		File refile = new File(baseDir, absFileName);
		return refile;
	}

	public static void compressedFileByPath(String rootPath, int type, boolean flag) {
		ZipOutputStream out = null;
		try {
			File dir = new File(TARGET_ZIP_FILE_DIR);
			if (!dir.exists()) {
				dir.mkdirs();
			}
			String path = TARGET_ZIP_FILE_DIR + File.separator + TARGET_ZIP_FILE.replace(".zip", "_" + type + ".zip");
			out = new ZipOutputStream(new FileOutputStream(path));
			compressedFile(out, new File(rootPath), "", flag, 0);
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(e.toString());
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private static int compressedFile(ZipOutputStream out, File tempFile, String base, boolean flag, int count) throws Exception {
		if (tempFile.isDirectory()) {
			File[] files = tempFile.listFiles();
			// 判断是否保留目录结构
			if (base.length() > 0) {
				base = base + File.separator;
				if (flag) {
					System.out.println("prepare compressed directory: " + base);
					out.putNextEntry(new ZipEntry(base));
					out.closeEntry();
				}
			} else {
				base = "";
			}
			for (int i = 0; i < files.length; i++) {
				count = compressedFile(out, files[i], base + files[i].getName(), flag, count);
			}
		} else {
			String realBase = null;
			if (!flag) {
				// 获取文件名和后缀
				String suffix = base.substring(base.lastIndexOf("."));
				String name = null;
				if (base.contains(File.separator)) {
					name = base.substring(base.lastIndexOf(File.separator) + 1, base.lastIndexOf("."));
				} else {
					name = base.substring(0, base.lastIndexOf("."));
				}
				realBase = name + "(" + count + ")" + suffix;
				count++;
			} else {
				realBase = base;
			}
			out.putNextEntry(new ZipEntry(realBase));
			System.out.println("prepare compressed file: " + realBase);
			FileInputStream in = null;
			try {
				in = new FileInputStream(tempFile);
				byte[] buf = new byte[1024];
				int b = 0;
				while ((b = in.read(buf)) != -1) {
					out.write(buf, 0, b);
					out.setEncoding("gbk");
				}
				out.closeEntry();
				in.close();
			} finally {
				if (in != null) {
					in.close();
				}
			}
		}
		return count;
	}

	/**
	 * 删除目录及目录下的所有子目录和文件
	 * 
	 * @param path 文件或目录路径
	 * @return
	 */
	public static boolean delFiles(String path) {
		boolean result = false;
		File file = new File(path);
		// 判断目录
		if (file.isDirectory()) {
			File[] childrenFiles = file.listFiles();
			for (File childFile : childrenFiles) {
				result = delFiles(childFile.getPath());
				if (!result) {
					return result;
				}
			}
		}
		// 删除 文件、空目录
		return file.delete();
	}
}
